package com.opengate.base.oss.helper;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.opengate.base.oss.config.OssConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.UUID;

/**
 * @author 曳戈泰尔
 * @version 1.0
 * @description OSS工具类
 * @date 2023/3/10 7:37 PM
 */
@Slf4j
public class OssHelper {

    /** 注入OSS配置类 */
    private final OssConfig ossConfig;

    public OssHelper(OssConfig ossConfig) {
        this.ossConfig = ossConfig;
    }

    /**
     * 上传到OSS服务器 自定义上传路经
     *
     * @param inStream 文件流
     * @param fileName 文件名称 包括后缀名
     * @param contentType 文件类型
     * @param filePath 上传目标文件夹
     * @return String 返回文件访问路径
     */
    public String uploadFile2OSS(
            InputStream inStream,
            String fileName,
            String contentType,
            String filePath,
            Long fileLength) {

        /** OSS客户端 */
        OSS ossClient =
                new OSSClientBuilder()
                        .build(
                                ossConfig.getEndPoint(),
                                ossConfig.getKeyId(),
                                ossConfig.getKeySecret());

        // 最终访问文件的路径
        String ret = null;

        try {

            // 判断bucket是否已经存在,不存在进行创建
            if (!doesBucketExist(ossClient)) {
                if (!createBucket(ossClient)) {
                    log.error("创建OSS文件桶失败");

                    return null;
                }
            }

            // 创建上传Object的Metadata
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setCacheControl("no-cache");
            objectMetadata.setHeader("Pragma", "no-cache");
            objectMetadata.setContentType(contentType);
            objectMetadata.setContentDisposition("inline;filename=" + fileName);
            objectMetadata.setContentLength(fileLength);

            // 指定上传文件操作时是否覆盖同名Object。
            // 不指定x-oss-forbid-overwrite时，默认覆盖同名Object。
            // 指定x-oss-forbid-overwrite为false时，表示允许覆盖同名Object。
            // 指定x-oss-forbid-overwrite为true时，表示禁止覆盖同名Object，如果同名Object已存在，程序将报错。
            objectMetadata.setHeader("x-oss-forbid-overwrite", "false");

            // 对象名称
            String objectName;
            if (StringUtils.isEmpty(filePath)) {
                objectName = ossConfig.getFileLocation() + "/" + fileName;
            } else {
                objectName = ossConfig.getFileLocation() + "/" + filePath + "/" + fileName;
            }

            // 上传文件
            ossClient.putObject(ossConfig.getBucketName(), objectName, inStream, objectMetadata);

            // 拼接 url 访问路径
            ret = ossConfig.getFileHost() + "/" + objectName;
            log.info("文件上传成功，文件名：{}，访问路径ret：{}", fileName, ret);

        } catch (Exception e) {
            log.error("OSS上传对象失败，原因：{}", e.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
            try {
                if (inStream != null) {
                    inStream.close();
                }
            } catch (Exception e) {
                log.error("OSS中关闭输入流失败，原因：{}", e.getMessage());
            }
        }

        return ret;
    }

    /**
     * 上传到OSS服务器
     *
     * @param inStream 文件流
     * @param fileName 文件名称 包括后缀名
     * @return String 返回文件访问路径
     */
    public String uploadFile2OSS(
            InputStream inStream, String fileName, String contentType, Long fileLength) {
        return uploadFile2OSS(inStream, fileName, contentType, "", fileLength);
    }

    /**
     * 上传文件到阿里云对象存储
     *
     * @param file 文件
     * @return String 返回文件访问路径
     */
    public String uploadFile2OSS(MultipartFile file) {

        try {
            // 获取文件名
            String fileName = file.getOriginalFilename();
            // 获取文件类型
            String contentType = file.getContentType();

            // 文件名不为空
            if (StringUtils.isBlank(fileName)) {
                log.error("获取MultipartFile文件名失败，文件名为空");

                return null;
            }
            if (StringUtils.isBlank(contentType)) {
                log.error("获取MultipartFile文件类型失败，文件类型为空");

                return null;
            }

            // 获取文件后缀
            String suffix;
            try {
                suffix = fileName.substring(fileName.lastIndexOf("."));
            } catch (Exception ex) {
                log.error("获取MultipartFile文件名失败，原因：{}", ex.getMessage());

                return null;
            }

            // 生成不重复的新文件名
            String newFileName = UUID.randomUUID() + suffix;

            return uploadFile2OSS(file.getInputStream(), newFileName, contentType, file.getSize());

        } catch (Exception ex) {
            log.error("上传MultipartFile文件到OSS失败，原因：{}", ex.getMessage());
        }

        return null;
    }

    /**
     * 上传文件到阿里云对象存储
     *
     * <p>url可能带 ? 拼接的参数
     *
     * @param fileUrl 文件访问路径
     * @return String 返回文件访问路径
     */
    public String uploadFile2OSS(String fileUrl) {

        try {

            // 获取文件后缀
            String suffix;
            try {
                suffix = fileUrl.substring(fileUrl.lastIndexOf("."));
                // 去除后缀 ? 拼接的参数
                if (suffix.contains("?")) {
                    suffix = suffix.substring(0, suffix.indexOf("?"));
                }
            } catch (Exception ex) {
                log.error("获取fileUrl文件后缀失败，原因：{}", ex.getMessage());

                return null;
            }

            // 生成不重复的新文件名
            String fileName = UUID.randomUUID() + suffix;

            // 获取上传文件文件流
            URL url = new URL(fileUrl);
            URLConnection urlConn = url.openConnection();
            InputStream inputStream = urlConn.getInputStream();

            // 获取文件类型
            String contentType = urlConn.getContentType();
            if (StringUtils.isBlank(contentType)) {
                log.error("获取fileUrl文件类型失败，文件类型为空");

                return null;
            }

            return uploadFile2OSS(
                    inputStream, fileName, contentType, urlConn.getContentLengthLong());

        } catch (Exception ex) {
            log.error("上传fileUrl文件到OSS失败，原因：{}", ex.getMessage());
        }

        return null;
    }

    /**
     * 删除文件
     *
     * @param fileName 文件名
     * @param filePath 文件路径
     * @return Boolean
     */
    public Boolean deleteFile(String fileName, String filePath) {

        /** OSS客户端 初始化 */
        OSS ossClient =
                new OSSClientBuilder()
                        .build(
                                ossConfig.getEndPoint(),
                                ossConfig.getKeyId(),
                                ossConfig.getKeySecret());

        try {
            // 对象名称
            String objectName;
            if (StringUtils.isEmpty(filePath)) {
                objectName = ossConfig.getFileLocation() + "/" + fileName;
            } else {
                objectName = ossConfig.getFileLocation() + "/" + filePath + "/" + fileName;
            }

            // 删除文件或目录。如果要删除目录，目录必须为空。
            VoidResult voidResult = ossClient.deleteObject(ossConfig.getBucketName(), objectName);
            log.info("删除OSS结果状态：{}", voidResult.getResponse().getStatusCode());

        } catch (Exception ex) {
            log.error("OSS删除文件失败，原因：{}", ex.getMessage());

            return false;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        return true;
    }

    /**
     * 判断Bucket是否存在
     *
     * @return 存在返回true
     */
    public boolean doesBucketExist(OSS ossClient) {
        return ossClient.doesBucketExist(ossConfig.getBucketName());
    }

    /** 创建Bucket */
    public boolean createBucket(OSS ossClient) {

        try {
            CreateBucketRequest createBucketRequest =
                    new CreateBucketRequest(ossConfig.getBucketName());
            // 设置bucket权限为公共读，默认是私有读写
            createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
            // 设置bucket存储类型为低频访问类型，默认是标准类型
            createBucketRequest.setStorageClass(StorageClass.Standard);

            ossClient.createBucket(createBucketRequest);

            return true;

        } catch (Exception ex) {
            log.error("创建OSS的bucket失败，原因：{}", ex.getMessage());
        }

        return false;
    }
}
